// Copyright 2024 Woodpecker Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package gitcode

import (
	"fmt"
	"io"
	"net/http"
	"strings"

	"github.com/rs/zerolog/log"

	"go.woodpecker-ci.org/woodpecker/v3/server/forge/types"
	"go.woodpecker-ci.org/woodpecker/v3/server/model"
)

const (
	hookEvent        = "X-Gitcode-Event"
	hookPush         = "Push Hook"
	hookTagPush      = "Tag Push Hook"
	hookMergeRequest = "Merge Request Hook"
	hookCreated      = "create"
	hookPullRequest  = "pull_request"
	hookRelease      = "release"

	actionOpen   = "opened"
	actionSync   = "synchronized"
	actionClose  = "closed"
	actionReopen = "reopened"
)

// parseHook parses a GitCode hook from an http.Request and returns
// Repo and Pipeline detail. If a hook type is unsupported nil values are returned.
func parseHook(r *http.Request) (*model.Repo, *model.Pipeline, error) {
	hookType := r.Header.Get(hookEvent)
	switch hookType {
	case hookPush:
		return parsePushHook(r.Body)
	case hookTagPush:
		return parseTagPushHook(r.Body)
	case hookMergeRequest:
		return parseMergeRequestHook(r.Body)
	case hookCreated:
		return parseCreatedHook(r.Body)
	case hookPullRequest:
		return parsePullRequestHook(r.Body)
	case hookRelease:
		return parseReleaseHook(r.Body)
	}
	log.Debug().Msgf("unsupported hook type: '%s'", hookType)
	return nil, nil, &types.ErrIgnoreEvent{Event: hookType}
}

// parsePushHook parses a push hook and returns the Repo and Pipeline details.
// If the commit type is unsupported nil values are returned.
func parsePushHook(payload io.Reader) (repo *model.Repo, pipeline *model.Pipeline, err error) {
	push, err := parsePush(payload)
	if err != nil {
		return nil, nil, err
	}

	// ignore push events for tags
	if strings.HasPrefix(push.Ref, "refs/tags/") {
		return nil, nil, nil
	}

	// 从 push hook 构建 Repository 对象
	repo = &model.Repo{
		ForgeRemoteID: model.ForgeRemoteID(fmt.Sprintf("%d", push.ProjectID)),
		Owner:         push.Project.Namespace,
		Name:          push.Project.Name,
		FullName:      push.Project.PathWithNamespace,
		Avatar:        push.Project.AvatarURL,
		ForgeURL:      push.Project.WebURL,
		Clone:         push.Project.GitHTTPURL,
		CloneSSH:      push.Project.GitSSHURL,
		Branch:        push.Project.DefaultBranch,
		IsSCMPrivate:  push.Project.VisibilityLevel == 0,
		Perm: &model.Perm{
			Pull:  true,
			Push:  true,
			Admin: false,
		},
	}

	pipeline = pipelineFromPush(push)
	return repo, pipeline, err
}

// parseCreatedHook parses a push hook and returns the Repo and Pipeline details.
// If the commit type is unsupported nil values are returned.
func parseCreatedHook(payload io.Reader) (repo *model.Repo, pipeline *model.Pipeline, err error) {
	push, err := parsePush(payload)
	if err != nil {
		return nil, nil, err
	}

	// 检查是否为标签推送
	if !strings.HasPrefix(push.Ref, "refs/tags/") {
		return nil, nil, nil
	}

	// 从 push hook 构建 Repository 对象
	repo = &model.Repo{
		ForgeRemoteID: model.ForgeRemoteID(fmt.Sprintf("%d", push.ProjectID)),
		Owner:         push.Project.Namespace,
		Name:          push.Project.Name,
		FullName:      push.Project.PathWithNamespace,
		Avatar:        push.Project.AvatarURL,
		ForgeURL:      push.Project.WebURL,
		Clone:         push.Project.GitHTTPURL,
		CloneSSH:      push.Project.GitSSHURL,
		Branch:        push.Project.DefaultBranch,
		IsSCMPrivate:  push.Project.VisibilityLevel == 0,
		Perm: &model.Perm{
			Pull:  true,
			Push:  true,
			Admin: false,
		},
	}

	pipeline = pipelineFromTag(push)
	return repo, pipeline, nil
}

// parsePullRequestHook parses a pull_request hook and returns the Repo and Pipeline details.
func parsePullRequestHook(payload io.Reader) (*model.Repo, *model.Pipeline, error) {
	var (
		repo     *model.Repo
		pipeline *model.Pipeline
	)

	pr, err := parsePullRequest(payload)
	if err != nil {
		return nil, nil, err
	}

	// GitCode 使用 merge_request 字段
	if pr.MergeRequest.ID == 0 {
		return nil, nil, fmt.Errorf("parsed pull_request webhook does not contain merge_request info")
	}

	// Don't trigger pipelines for non-code changes ...
	action := pr.MergeRequest.Action
	if action != "open" &&
		action != "update" &&
		action != "close" &&
		action != "reopen" {
		log.Debug().Msgf("pull_request action is '%s' and not supported", action)
		return nil, nil, nil
	}

	repo = repoFromPullRequestHook(pr)
	pipeline = pipelineFromPullRequestHook(pr)
	return repo, pipeline, err
}

// parseTagPushHook parses a tag push hook and returns the Repo and Pipeline details.
func parseTagPushHook(payload io.Reader) (*model.Repo, *model.Pipeline, error) {
	push, err := parsePush(payload)
	if err != nil {
		return nil, nil, err
	}

	// 确保这是标签推送
	if !strings.HasPrefix(push.Ref, "refs/tags/") {
		log.Debug().Msgf("Tag Push Hook received but ref is not a tag: %s", push.Ref)
		return nil, nil, nil
	}

	// 从 push hook 构建 Repository 对象
	repo := &model.Repo{
		ForgeRemoteID: model.ForgeRemoteID(fmt.Sprintf("%d", push.ProjectID)),
		Owner:         push.Project.Namespace,
		Name:          push.Project.Name,
		FullName:      push.Project.PathWithNamespace,
		Avatar:        push.Project.AvatarURL,
		ForgeURL:      push.Project.WebURL,
		Clone:         push.Project.GitHTTPURL,
		CloneSSH:      push.Project.GitSSHURL,
		Branch:        push.Project.DefaultBranch,
		IsSCMPrivate:  push.Project.VisibilityLevel == 0,
		Perm: &model.Perm{
			Pull:  true,
			Push:  true,
			Admin: false,
		},
	}

	pipeline := pipelineFromTag(push)
	return repo, pipeline, nil
}

// parseMergeRequestHook parses a merge request hook and returns the Repo and Pipeline details.
func parseMergeRequestHook(payload io.Reader) (*model.Repo, *model.Pipeline, error) {
	// GitCode 的 Merge Request Hook 可能与 Pull Request Hook 使用相似的数据结构
	// 先尝试作为 Pull Request 解析
	return parsePullRequestHook(payload)
}

// parseReleaseHook parses a release hook and returns the Repo and Pipeline details.
func parseReleaseHook(payload io.Reader) (*model.Repo, *model.Pipeline, error) {
	var (
		repo     *model.Repo
		pipeline *model.Pipeline
	)

	release, err := parseRelease(payload)
	if err != nil {
		return nil, nil, err
	}

	repo = toRepo(release.Repo)
	pipeline = pipelineFromRelease(release)
	return repo, pipeline, err
}
